(ns gui
  (:use vigenere)
  (:require [clojure.contrib.java-utils :as file])
  (:require [clojure.contrib.string :as str]))

(import '(javax.swing JFrame JPanel JLabel JTextField JButton JTextArea 
                     JComboBox JScrollPane JComponent GroupLayout Box 
                     GroupLayout$Alignment LayoutStyle$ComponentPlacement 
                     JOptionPane UIManager JTable  table.DefaultTableModel 
                     table.DefaultTableCellRenderer))
(import '(java.awt event.ActionListener Image Toolkit Graphics 
                   Frame Component Font Color))
(import 'javax.imageio.ImageIO)

;On Stuart Sierra's blog i saw how to start with swing components: http://stuartsierra.com/2010/01/02/first-steps-with-clojure-swing
(def button-solve (doto (JButton. "Solve")
                        (. setAlignmentX Component/CENTER_ALIGNMENT)))

(def button-clear (doto (JButton. "Clear all")
                        (. setAlignmentX Component/CENTER_ALIGNMENT)))

(def font (Font. "Calibri" Font/PLAIN  16))

(def text-area (doto (JTextArea.)
                     (.setColumns 10)
                     (.setRows 10)
                     (.setLineWrap true)
                     (.setWrapStyleWord true)
                     (.setFont font)))

(def output-text (doto (JTextArea.)
                       (.setColumns 10)
                       (.setRows 10)
                       (.setLineWrap true)
                       (.setWrapStyleWord true)
                       (.setFont font)
                       (.setEditable false)))

(def scroll-pane (doto (JScrollPane.)
                       (.setViewportView text-area)))

(def scroll-pane-output (doto (JScrollPane.)
                       (.setViewportView output-text)))

(def combobox (doto (JComboBox.)
                    (.addItem "Code to Vigenere cipher")
                    (.addItem "Decode from Vigenere cipher")))

(def key-word (doto (JTextField.)
                       (.setFont font)))

(def key-label (JLabel. "Keyword:"))

(def output-label (JLabel. "Output text:"))
                      
(def key-table  (JLabel. "Vigenere table for keyword:"))
 
(def input-label (JLabel. "Input text:"))

(def code-label  (JLabel. "Choose code/decode:"))

(def intro-label (doto(JLabel. )
                  (.setText "<html><i>\"Laughter is the cipher key wherewith we decipher the whole man\"</i></html>")
                  (.setFont (Font. "Times New Roman" Font/BOLD  16))))

;Great article where i learnt how to use proxy-super: http://www.gettingclojure.com/articles:extending-java-classes-using-proxy
(def background-image 
   (let [panel (proxy [JPanel][]
    (paintComponent [#^Graphics g]
     (proxy-super paintComponent g)
     (doto g
      (.drawImage (ImageIO/read (file/as-file "resources/background.jpg")) 0 0 nil))
     ))]
     (doto panel
       (.revalidate))))

(def default-table-model
  (let [model (proxy [DefaultTableModel][]
               (isCellEditable [row column] false))]
    (loop [columns vigenere-row]
      (if (empty? columns)
     model 
          (do (.addColumn model (str (first columns)))
              (recur (rest columns)))))))

(def renderer
  (doto (DefaultTableCellRenderer.)
        (.setHorizontalAlignment DefaultTableCellRenderer/CENTER)))


(def table-vigenere 
 (let[table(doto 
            (JTable. default-table-model)
            (.setAutoResizeMode JTable/AUTO_RESIZE_ALL_COLUMNS))]
    (loop [counter 0]
            (if (= counter (.getColumnCount default-table-model))
       table
              (do (.setDefaultRenderer table (.getColumnClass table counter) renderer)
                  (recur (+ counter 1)))))))


(defn add-keyrow "Adds keyrow to the end of table." [keyrows]
  (do  (.setRowCount default-table-model 0)
  (loop [rows (rseq(vec(vals keyrows)))]
  (if (empty? rows)
     nil 
      (do (.addRow default-table-model (into-array(first rows)))
          (recur (rest rows)))))))


(def table-scrollpane
  (doto (JScrollPane.)
        (.setViewportView table-vigenere)))

  
(def groupLayout (doto (GroupLayout. background-image)
                       (.setAutoCreateContainerGaps true)))

;On official Clojure pages i learnt how to use some Java Interop: http://clojure.org/java_interop
(doto groupLayout 
 (.setHorizontalGroup 
  (.. groupLayout (createSequentialGroup) (addGap 10)
                  (addGroup 
                   (.. groupLayout(createParallelGroup GroupLayout$Alignment/LEADING) 
                        (addGroup (.. groupLayout (createSequentialGroup)(addGap 190)(addComponent intro-label)))
                        (addGroup (.. groupLayout (createSequentialGroup)
                                      (addGap 70)(addComponent code-label) (addGap 115) 
                                      (addComponent combobox GroupLayout/PREFERRED_SIZE 200 GroupLayout/PREFERRED_SIZE))) 
                        (addGroup (.. groupLayout (createSequentialGroup)
                                      (addComponent input-label) (addGap 10) 
                                      (addComponent scroll-pane GroupLayout/PREFERRED_SIZE 300 GroupLayout/PREFERRED_SIZE) (addGap 10)
                                      (addComponent output-label) (addGap 10) 
                                      (addComponent scroll-pane-output GroupLayout/PREFERRED_SIZE 300 GroupLayout/PREFERRED_SIZE))) 
                        (addGroup (.. groupLayout (createSequentialGroup)(addGap 70)
                                      (addComponent key-label) (addGap 40)
                                      (addComponent key-word GroupLayout/PREFERRED_SIZE 200 GroupLayout/PREFERRED_SIZE)(addGap 90) 
                                      (addComponent button-clear GroupLayout/PREFERRED_SIZE 200 GroupLayout/PREFERRED_SIZE)))
                        (addGroup (.. groupLayout (createSequentialGroup) (addGap 40) (addComponent key-table) (addGap 120)
                                      (addComponent button-solve GroupLayout/PREFERRED_SIZE 200 GroupLayout/PREFERRED_SIZE)))
                        (addGroup (.. groupLayout (createSequentialGroup) (addGap 40)(addComponent table-scrollpane)))))))
  (.setVerticalGroup 
    (.. groupLayout (createSequentialGroup) (addGap 40)(addComponent intro-label) (addGap 20) 
                    (addGroup 
                     (.. groupLayout (createParallelGroup GroupLayout$Alignment/BASELINE)
                          (addComponent code-label) 
                          (addComponent combobox GroupLayout/PREFERRED_SIZE 20 GroupLayout/PREFERRED_SIZE)))(addGap 20)
                   (addGroup 
                     (.. groupLayout (createParallelGroup GroupLayout$Alignment/LEADING)
                         (addComponent input-label) (addComponent scroll-pane GroupLayout/PREFERRED_SIZE 250 GroupLayout/PREFERRED_SIZE) (addComponent output-label) (addComponent scroll-pane-output GroupLayout/PREFERRED_SIZE 250 GroupLayout/PREFERRED_SIZE)))
                        (addGap 20) 
                        (addGroup (.. groupLayout (createParallelGroup GroupLayout$Alignment/LEADING)
                                      (addComponent key-label)
                                      (addComponent key-word GroupLayout/PREFERRED_SIZE 20 GroupLayout/PREFERRED_SIZE)
                                      (addComponent button-clear))) (addGap 20) 
                        (addGroup (.. groupLayout (createParallelGroup GroupLayout$Alignment/LEADING)(addComponent key-table)
                                      (addComponent button-solve)))(addGap 10)(addComponent table-scrollpane))))
                  
(doto background-image (.setLayout groupLayout))  

;On this page i found how to get time, not just to print him with time macro: http://stackoverflow.com/questions/3041299/how-to-benchmark-functions-in-clojure
(defn set-text "Sets a text to textarea from vector of vectors." [vectors begining-time]
 (let [coding-time (/ (- (. System (nanoTime)) begining-time) 1000000.0) print-start (. System (nanoTime))]
  (loop [elements vectors]
   (if (empty? elements)
      [(str coding-time " msecs") (str(/ (- (. System (nanoTime)) print-start) 1000000.0) " msecs")]
       (do (.append output-text (apply str (first elements)))
           (recur (rest elements)))))))


(defn check-input "Checks input text for errors." [text key-word]
  (cond (= "" text)
        ["You need to enter text!" "Input text"]
        (= "" key-word)
        ["You need to enter keyword!" "Keyword"]
        (= nil (check-letters key-word))
        ["Keyword contains some special characters!" "Keyword"]
        (< (.length text)(.length key-word))
        ["Input text need to be longer than keyword!" "Input text"]
        :else nil))



(defn solve "Solving vigenere cipher and printing solution and informational parameters." []
 (let [check (check-input (.getText text-area)(.getText key-word))  
                begining-time (. System (nanoTime))]
  (if(= nil check)
     (do (.setText output-text "")
         (.setEnabled button-solve false) 
         (.setEnabled button-clear false)
         (.setEditable output-text false)  
         (future(let [translate-time 
                 (if  (= (.getSelectedIndex combobox) 0)  
                   (set-text (map-in-parallel (.getText text-area) (.getText key-word) 
                              code-vigenere) begining-time)
                   (set-text (map-in-parallel (.getText text-area) (.getText key-word)
                              decode-vigenere) begining-time))] 
                 (do(.setEnabled button-solve true) 
                    (.setEnabled button-clear true)  
                    (JOptionPane/showMessageDialog nil 
                      (str "Finished!\nTime to solve: " (translate-time 0) 
                           "\nTime to print whole text: " (translate-time 1)) "Vigenere cipher"
     JOptionPane/INFORMATION_MESSAGE))))
        (add-keyrow (vigenere-square (.getText key-word))))
    (JOptionPane/showMessageDialog nil (check 0) (check 1)
     JOptionPane/INFORMATION_MESSAGE))))

(defn clear-all "Clears all text areas and textfield."[]
  (do 
      (.setText text-area  "") 
      (.setText key-word  "")
      (.setText output-text  "")
      (.setRowCount default-table-model 0)))

(def action-solve 
 (proxy [ActionListener] []
   (actionPerformed [event] (solve))))

(def action-clear 
 (proxy [ActionListener] []
  (actionPerformed [event] (clear-all))))

(.addActionListener button-solve action-solve)

(.addActionListener button-clear action-clear)

(defn main []
 (def frame (doto (JFrame. "Vigenere cipher")
                  (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
                  (.setSize 800 600)
                  (.setContentPane background-image)
                  (.setResizable false)
                  (.setLocationRelativeTo nil)
                  (.validate)
                  (.setVisible true))))